/*
 * Unidata Platform Community Edition
 * Copyright (c) 2013-2020, UNIDATA LLC, All rights reserved.
 * This file is part of the Unidata Platform Community Edition software.
 *
 * Unidata Platform Community Edition is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Unidata Platform Community Edition is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 */

package org.unidata.mdm.data.service.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import org.apache.commons.collections4.MapUtils;
import org.springframework.stereotype.Component;
import org.unidata.mdm.core.type.data.ArrayAttribute;
import org.unidata.mdm.core.type.data.ArrayValue;
import org.unidata.mdm.core.type.data.Attribute;
import org.unidata.mdm.core.type.data.DataRecord;
import org.unidata.mdm.core.type.data.SimpleAttribute;
import org.unidata.mdm.core.type.model.AttributeElement;
import org.unidata.mdm.core.type.model.EntityElement;
import org.unidata.mdm.core.type.model.LookupElement;
import org.unidata.mdm.core.util.SecurityUtils;
import org.unidata.mdm.data.type.keys.RecordKeys;
import org.unidata.mdm.meta.configuration.Descriptors;
import org.unidata.mdm.meta.type.search.EntityIndexType;
import org.unidata.mdm.meta.type.search.RecordHeaderField;
import org.unidata.mdm.search.context.SearchRequestContext;
import org.unidata.mdm.search.type.form.FieldsGroup;
import org.unidata.mdm.search.type.form.FormField;
import org.unidata.mdm.search.type.query.SearchQuery;

/**
 * @author Mikhail Mikhailov on Nov 5, 2019
 */
@Component("recordValidationComponent")
public class RecordValidationComponent extends AbstractValidationComponent {
    /**
     * Constructor.
     */
    public RecordValidationComponent() {
        super();
    }

    public void checkRecordsDataRecord(DataRecord record, String id) {
        checkDataRecord(record, id);
    }

    public Map<AttributeElement, Collection<Object>> checkMissedLookupValues(RecordKeys keys, DataRecord record) {

        if (Objects.isNull(keys)) {
            return Collections.emptyMap();
        }

        Map<LookupElement, Set<AttributeElement>> toLookups = null;
        String entityName = keys.getEntityName();

        EntityElement element = metaModelService
                .instance(Descriptors.DATA)
                .getElement(entityName);

        if (element.isLookup()) {
            toLookups = element.getLookup().getReferencedLookups();
        } else if (element.isRegister()) {
            toLookups = element.getRegister().getReferencedLookups();
        }

        if (MapUtils.isEmpty(toLookups)) {
            return Collections.emptyMap();
        }

        Map<AttributeElement, Collection<Object>> result = new HashMap<>();
        for (Map.Entry<LookupElement, Set<AttributeElement>> entry : toLookups.entrySet()) {
            for (AttributeElement attributeInfo : entry.getValue()) {
                Collection<Attribute> attrs = record.getAttributeRecursive(attributeInfo.getPath());
                for (Attribute attribute : attrs) {
                    switch (attribute.getAttributeType()) {
                        case SIMPLE:
                            SimpleAttribute<?> simpleAttribute = (SimpleAttribute<?>) attribute;
                            Object ref = simpleAttribute.getValue();
                            if (ref == null) {
                                break;
                            }
                            SearchRequestContext requestContext = getLookupEntityValueSearchRequest(entry.getKey(), ref);
                            if (searchService.search(requestContext).getTotalCount() == 0) {
                                result.computeIfAbsent(attributeInfo, k -> new ArrayList<Object>()).add(ref);
                            }
                            break;
                        case ARRAY:
                            ArrayAttribute<?> arrayAttribute = (ArrayAttribute<?>) attribute;
                            for (ArrayValue<?> arrayValue : arrayAttribute) {
                                if (arrayValue.getValue() == null) {
                                    continue;
                                }
                                SearchRequestContext requestCtx = getLookupEntityValueSearchRequest(entry.getKey(), arrayValue.getValue());
                                if (searchService.search(requestCtx).getTotalCount() == 0) {
                                    result.computeIfAbsent(attributeInfo, k -> new ArrayList<Object>()).add(arrayValue.getValue());
                                }
                            }
                            break;
                        default:
                            break;
                    }
                }
            }
        }

        return result;
    }

    /**
     * Search the specified lookup entities by code attribute value.
     *
     * @param lookupEntityDef lookup entity definition
     * @param codeAttrValue code attribute value
     * @return search request
     */
    protected SearchRequestContext getLookupEntityValueSearchRequest(EntityElement lookup, Object codeAttrValue) {
        return SearchRequestContext.builder(EntityIndexType.RECORD, lookup.getName(), SecurityUtils.getCurrentUserStorageId())
                .returnFields(Collections.singletonList(lookup.getLookup().getCodeAttribute().getPath()))
                .totalCount(true)
                .countOnly(true)
                .count(1)
                .query(SearchQuery.formQuery(FieldsGroup.and(
                        FormField.exact(RecordHeaderField.FIELD_DELETED, false),
                        FormField.exact(lookup.getLookup().getCodeAttribute().getIndexed(), codeAttrValue))))
                .build();
    }
}
